home *** CD-ROM | disk | FTP | other *** search
/ MacWorld 1997 September / Macworld (1997-09).dmg / Shareware World / Utilities / User Interface / Sloop 68K 1.0.2 / Sloop Extras / PopApp Source 1.0b2.sit / PopApp Source 1.0b2 / popApp.c next >
C/C++ Source or Header  |  1997-07-13  |  13KB  |  421 lines

  1. #pragma mark Top of file
  2. /*
  3.         PopApp (Application popup menu)
  4.         Version 1.0b2
  5.         ©1997 Graham Herrick
  6.         
  7.             This source code may be freely distributed without modification
  8.         as long as this notice remains intact.
  9.             If you use this code without modification in any software -- whether
  10.         freeware, shareware, or commercial, you should acknowledge my copyright
  11.         in your documentation and "about box".
  12.             You may also modify this code as you see fit. If you modify it and
  13.         still want to attribute it to me, please make note of the fact that your
  14.         project includes a "modified" version of this code.
  15.         
  16.         Part of *this* code (the assembly shell) uses modified sample
  17.         code from Apple DTS (jGNEHelper), the only modification being to
  18.         fix the nil filter bug.
  19.         
  20.         Obviously, the ShowInitIcon code by Peter Lewis, Jim Walker, and
  21.         François Pottier isn't mine either -- see that file for more info.
  22.         
  23.         If you find any bugs in my code, please let me know. This is still a beta version.
  24.         
  25.         Here are the CodeWarrior project settings in case you don't have the project file
  26.         or you don't have CW 12 (CW Pro 1). Make a single 68K target.
  27.         Include:
  28.             popApp.c
  29.             ShowInitIcon.c
  30.             MacOS.lib
  31.             PopApp.µ.rsrc
  32.         68K Target settings:
  33.             Project type:    Code Resource
  34.             Creator:        PopA
  35.             Type:            INIT
  36.             ResType:        INIT
  37.             ResID:            128
  38.             Res Flags:        System heap and locked
  39.             Header type:    Standard
  40.             Check boxes unchecked
  41.         C/C++ Language
  42.             Just leave it at CodeWarrior default
  43.             (everything unchecked except require function prototypes)
  44.             Prefix whatever you want or use default as long as Universal headers are prefixed somehow
  45.         C/C++ Warnings
  46.             I always like to turn everything on
  47.         68K Processor
  48.             Code model:        Small
  49.             Struct Align:    68K
  50.             Float:            doesn't matter
  51.             Check these boxes:
  52.             4 byte ints
  53.             8 byte doubled
  54.             68020 Codegen if you want
  55.             Peephole and CSE optimizers
  56.             Optimize for size
  57.             PC-relative strings
  58.             Global register allocation
  59.         68K Linker
  60.             Symbols:        None (unless you want them of course)
  61.             SYM file: do what you want
  62.             Do not generate A6 stack frames
  63.             Fast link
  64.             Link single segment
  65.             Merge compiler glue
  66.             Dead-strip static initialization code
  67.         That's it. The most important things are:
  68.             All 68K target settings, small code model, 68K structs, link single segment and merge glue
  69.         
  70.         Version history:
  71.         1997-05-10    Wrote the initial code
  72.         1997-05-13    Removed some things
  73.         1997-05-14    Started to clean it up for public release
  74.         1997-07-01    Reverted assembly to be closer to Apple sample code
  75.         1997-07-13    Cleaned up a bit, wrote some more comments -- ready to release with Sloop 1.0.2
  76. */
  77.  
  78. #pragma mark Includes
  79. // Note: This is essentially a CodeWarrior file
  80. // You'll have to redo a fair amount to make it work with something else
  81. // It's also, obviously, only 68K code. PPC jGNEFilters are different (and more difficult)
  82. #include <SetUpA4.h>
  83. #include <A4Stuff.h>
  84. #include "ShowInitIcon.h"
  85.  
  86. #pragma mark Constants
  87. #define kMyMenuID            23451    // this is arbitary -- it should actually check to find a free ID
  88.                                     // on the off chance that someone else is using this ID
  89. #define kAppMenuID            -16489
  90. #define resGoodInitIcon        128
  91. #define resBadInitIcon        129
  92.  
  93. #pragma mark Types
  94. typedef struct
  95. {
  96.     MenuHandle        menuoH;            // menu handle
  97.     SInt16            menuLeft;        // left edge of menu
  98. } MenuRec;
  99.  
  100. typedef struct
  101. {
  102.     SInt16            lastMenu;        // offset to last menu handle
  103.     SInt16            lastRight;        // right edge of last menu's title
  104.     SInt16            notUsed;
  105.     MenuRec            mArray[];        // array of menus
  106. } MenuList, *MenuListPtr, **MenuListHdl;
  107.  
  108. #pragma mark Globals
  109. MenuHandle            myMenu;
  110. void                *gOldJGNE;
  111. Boolean                inJGNE;
  112.  
  113. #pragma mark Prototypes
  114. // jGNE Stuff
  115. pascal asm void myJGNEShell(void);
  116. Boolean myGNE(EventRecord *event, Boolean result);
  117. // Menu handling
  118. MenuHandle FindTheAppMenu(MenuListHdl theList);
  119. SInt32 SetUpTheMenu(MenuHandle appMenu, SInt32 *sepItemNum);
  120. void TearDownTheMenu(SInt32 numItems);
  121. void RespondToTheMenu(SInt16 item, SInt32 numItems, MenuHandle appMenu, SInt32 sepItemNum);
  122. // String utilities
  123. Boolean pStrComp(StringPtr p1, StringPtr p2);
  124. void pStrCopy(StringPtr p1, StringPtr p2);
  125.  
  126. #pragma mark Macros
  127. #define IsValidHandle(h)            (h && HandleZone((Handle)h) && !MemError() && *h)
  128. #define StuffIntoLong(high,low)        (((((UInt32)high)<<16) & 0xFFFF0000)|(0x0000FFFF & (UInt32)low))
  129. #define HighWord(n)                    ((n>>16) & 0x0000FFFF)
  130. #define LowWord(n)                    (n & 0x0000FFFF)
  131.  
  132. #pragma mark -
  133.  
  134. // This gets called when the INIT starts up
  135.  
  136. void main(void)
  137. {
  138.     Handle        myInit = 0L;
  139.     THz            theZ;
  140.     SInt32        oldA4;
  141.     
  142.     // set up stuff
  143.     oldA4 = SetCurrentA4();
  144.     RememberA4();
  145.     myMenu = 0L;
  146.     
  147.     // make sure the resource itself is marked system heap and locked!
  148.     myInit = Get1Resource('INIT',128);
  149.     if (!myInit) goto fail;
  150.     
  151.     // allocate globals in the system heap
  152.     theZ = GetZone();
  153.     SetZone(SystemZone());
  154.     myMenu = NewMenu(kMyMenuID,"\pPopApp Menu");
  155.     SetZone(theZ);
  156.     if (!myMenu) goto fail;
  157.     inJGNE = false;
  158.     
  159.     // install the jGNE filter
  160.     gOldJGNE = LMGetGNEFilter();
  161.     LMSetGNEFilter(myJGNEShell);
  162.     
  163.     // detach our code -- note this is not done until possible failures have been
  164.     // taken into account. If the code goes to "fail" before this then the INIT
  165.     // will be deallocated automatically when the file is closed during the startup sequence
  166.     DetachResource(myInit);
  167.     
  168.     // display the good startup icon
  169.     ShowInitIcon(resGoodInitIcon,true);
  170.     goto end;
  171. fail:
  172.     // deallocate globals if something failed
  173.     if(myMenu) DisposeMenu(myMenu);
  174.     // and show the bad icon
  175.     ShowInitIcon(resBadInitIcon,true);
  176. end:
  177.     // clean up before leaving
  178.     SetA4(oldA4);
  179. }
  180.  
  181. // This is a shell jGNEFilter based on Apple sample code
  182. // I just added two instructions to check for nil jGNEFilters
  183.  
  184. pascal asm void myJGNEShell(void)
  185. {
  186.     // on input: a1 has event record ptr, d0 has result
  187.     movea.l        d0,a0
  188.     jsr            SetUpA4
  189.     move.l        d0,-(sp)        // save old A4 on the stack
  190.     move.l        a0,d0
  191.     tst.b        inJGNE            // don't be reentrant
  192.     bne.s        skipOver
  193.     move.b        #true,inJGNE
  194.     move.w        d0,-(sp)        // push parameters
  195.     move.l        a1,-(sp)
  196.     jsr            myGNE            // call the C function below
  197.     movea.l        (sp)+,a1
  198.     addq.l        #2,sp
  199.     asl.w        #8,d0            // emulate GNE bug ("bump C Boolean to Lisa")
  200.     move.w        d0,0x0008(sp)
  201.     move.b        #false,inJGNE
  202. skipOver:
  203.     movea.l        gOldJGNE,a0        // get previous jGNE
  204.     movea.l        (sp)+,a4        // restore A4
  205.     // I added these two instructions to check for a nil jGNEFilter
  206.     cmpa.l        #0,a0            // is the previous jGNEFilter nil?
  207.     beq.s        bail            // if so, then bail
  208.     move.l        a0,-(sp)        // return to previous jGNE filter
  209. bail:
  210.     rts                            // return to caller of my jGNE
  211. }
  212.  
  213. // This is the C function called from the assembly code shell above
  214. // It is the main function for the filter
  215.  
  216. Boolean myGNE(EventRecord *event, Boolean preResult)
  217. {
  218.     Boolean postResult = preResult;
  219.     
  220.     if (preResult                                    // another jGNE didn't process this already
  221.             && (event->what == mouseDown)            // it's a mouse down event
  222.             && (event->modifiers & cmdKey)
  223.             && (event->modifiers & controlKey))        // with some modifier keys held down
  224.     {
  225.         // this is the current menu bar -- do not modify this since it isn't a copy!
  226.         MenuListHdl        theList = (MenuListHdl)LMGetMenuList();
  227.         
  228.         if (theList && *theList && myMenu && *myMenu)            // make sure everything seems to be OK
  229.         {
  230.             MenuHandle    appMenu;
  231.             
  232.             // Get the application menu
  233.             // Since this already has a nice list of applications,
  234.             // I just borrow the items from this list to save time and space
  235.             // Note: this is also not a copy so don't modify it!
  236.             // (Kind of a nasty hack but it works -- and it also gives us notification mgr stuff)
  237.             appMenu = FindTheAppMenu(theList);
  238.             if (appMenu)
  239.             {
  240.                 SInt32        numItems, sepItem;
  241.                 SInt32        mResult;
  242.                 
  243.                 // this is where we borrow the items and set up our menu
  244.                 numItems = SetUpTheMenu(appMenu, &sepItem);
  245.                 if (numItems)
  246.                 {
  247.                     // here's the actual pop-up menu!
  248.                     mResult = PopUpMenuSelect(myMenu,event->where.v,event->where.h,1);
  249.                     // get rid of it as soon as possible
  250.                     TearDownTheMenu(numItems);
  251.                     // now respond to whatever was selected (if something was)
  252.                     if (HighWord(mResult)==kMyMenuID)
  253.                         RespondToTheMenu(LowWord(mResult), numItems, appMenu, sepItem);
  254.                 }
  255.                 // make sure the mouse down doesn't fall through and get processed by anything
  256.                 event->what = nullEvent;
  257.                 event->modifiers = 0L;
  258.                 postResult = false;
  259.             }
  260.         }
  261.     }
  262.     return postResult;
  263. }
  264.  
  265. // this just looks at the current menu bar and gets the application system menu
  266. // it returns 0 if it couldn't be found for some reason
  267.  
  268. MenuHandle FindTheAppMenu(MenuListHdl theList)
  269. {
  270.     MenuHandle    temp;
  271.     SInt32        numMenus = (*theList)->lastMenu/6;    // 6 bytes per menu in mArray
  272.     
  273.     while(numMenus--)
  274.     {
  275.         temp = (*theList)->mArray[numMenus].menuoH;
  276.         // note: we need to check more than just that the handle is non-null since
  277.         // some things add weird stuff to the menu bar at times
  278.         if(IsValidHandle(temp)&&(*temp)->menuID==kAppMenuID) return temp;
  279.     }
  280.     return 0L;
  281. }
  282.  
  283. // this sets up the popup menu by copying the application names from the application menu
  284.  
  285. SInt32 SetUpTheMenu(MenuHandle appMenu, SInt32 *sepItemNum)
  286. {
  287.     SInt32        appItemNum, myItemNum = 0L, numAppItems = CountMItems(appMenu);
  288.     Str255        text;
  289.     SInt16        mark;
  290.     
  291.     for(appItemNum=1L;appItemNum<=numAppItems;appItemNum++)
  292.     {
  293.         // get the item info
  294.         GetMenuItemText(appMenu,appItemNum,text);
  295.         if(!myItemNum)
  296.         {
  297.             // skip the items before (and including) the separator
  298.             // we could hard code this and just start at item 5 but
  299.             // what if the format of the application menu changes?
  300.             // since we use this again later, save off the separator
  301.             // item number so we don't have to do this again
  302.             if(*text==1 && text[1]=='-')
  303.             {
  304.                 myItemNum++;
  305.                 *sepItemNum = appItemNum;
  306.             }
  307.             continue;
  308.         }
  309.         GetItemMark(appMenu,appItemNum,&mark);
  310.         
  311.         // add this item to our menu
  312.         AppendMenu(myMenu,"\p ");
  313.         SetMenuItemText(myMenu,myItemNum,text);
  314.         if(mark==noMark || mark==checkMark || mark==diamondMark)
  315.         {
  316.             // this adds the check mark for the front application
  317.             // and also will add diamond marks for notification manager things
  318.             SetItemMark(myMenu,myItemNum,mark);
  319.             // make sure it's enabled
  320.             EnableItem(myMenu,myItemNum);
  321.         }
  322.         else
  323.         {
  324.             // we've got a submenu or something that needs to be dealt with
  325.             // so don't deal with it
  326.             DisableItem(myMenu,myItemNum);
  327.         }
  328.         myItemNum++;
  329.     }
  330.     // add our menu to the current menu list in preparation for popping up
  331.     if(myItemNum)
  332.     {
  333.         InsertMenu(myMenu,hierMenu);
  334.         myItemNum--;    // remember we incremented at the end of the loop
  335.                         // note: if separator is last item this function still returns 0
  336.     }
  337.     return myItemNum;
  338. }
  339.  
  340. // this just cleans up and should be self-explanatory
  341.  
  342. void TearDownTheMenu(SInt32 numItems)
  343. {
  344.     DeleteMenu(kMyMenuID);    // remove from the menu list
  345.     while(numItems--)DeleteMenuItem(myMenu,1);    // clear it out internally
  346. }
  347.  
  348. // this processes a hit on our menu
  349.  
  350. void RespondToTheMenu(SInt16 item, SInt32 numItems, MenuHandle appMenu, SInt32 sepItemNum)
  351. {
  352.     SInt16        mark;
  353.     
  354.     // make sure its a valid hit
  355.     if (item > numItems || item < 1) return;
  356.     // adjust for looking into the application menu (see SetUpTheMenu)
  357.     item += sepItemNum;
  358.     // get the mark from the application menu (we've already destroyed our menu)
  359.     GetItemMark(appMenu,item,&mark);
  360.     // we only switch if its not frontmost (i.e. no check mark) and not hierarchical
  361.     if (mark==noMark || mark==diamondMark)
  362.     {
  363.         Str63                    hitName, findName;
  364.         ProcessSerialNumber        psn;
  365.         ProcessInfoRec            info;
  366.         OSErr                    myErr = noErr;
  367.         
  368.         // get the application name
  369.         GetMenuItemText(appMenu,item,hitName);
  370.         // now get the serial number of that application
  371.         psn.lowLongOfPSN = kNoProcess;
  372.         psn.highLongOfPSN = 0L;
  373.         while(!myErr)
  374.         {
  375.             myErr = GetNextProcess(&psn);
  376.             if (!myErr)
  377.             {
  378.                 // make sure these are reset before each info call since they get trashed by the call
  379.                 info.processInfoLength = sizeof(ProcessInfoRec);
  380.                 info.processName = findName;
  381.                 info.processAppSpec = nil;
  382.                 myErr = GetProcessInformation(&psn,&info);
  383.                 if (!myErr && pStrComp(findName,hitName))
  384.                 {
  385.                     // we got its serial number!
  386.                     // now the real response: bring that application to the front
  387.                     // note that, since we're using the system application menu,
  388.                     // we won't be bringing a background-only app to the front
  389.                     SetFrontProcess(&psn);
  390.                     break;
  391.                 }
  392.             }
  393.         }
  394.     }
  395. }
  396.  
  397. // I use these string utilities a lot
  398. // they are fast but do not do any error checking
  399.  
  400. // This is a byte by byte comparison of two strings so it obviously
  401. // doesn't handle capitalization, diacritical marks, etc.
  402.  
  403. Boolean pStrComp(StringPtr p1, StringPtr p2)
  404. {
  405.     register SInt32        ln = *p1;
  406.     do{if(*p1++!=*p2++)return false;}while(ln--);
  407.     return true;
  408. }
  409.  
  410. // This copies p1 into p2
  411. // Make sure beforehand that you have enough space in p2!
  412.  
  413. void pStrCopy (StringPtr p1, StringPtr p2)
  414. {
  415.     register SInt32        ln = *p1;
  416.     do{*p2++=*p1++;}while(ln--);
  417.     // note: you could also do BlockMoveData((Ptr)p1,(Ptr)p2,*p1+1)
  418.     // but why use the toolbox overhead for a few bytes of copying?
  419. }
  420.  
  421.